Dart 语法入门 II

函数 Functions

Dart 是一门面向对象的语言,即使函数也是对象也有类型。这意味着函数可以分配给变量或者当作参数传给其他函数。也可以像函数一样调用一个类的实例,比如 可调用的 Classes
下面是定义了一个函数:

bool isNoble(int atomicNumber) { // 不指定函数返回类型也可以执行,但不推荐这样做
  return _nobleGases[atomicNumber] != null;
}

// 可以用箭头语法描述单个表达式,但不能使用多行的语句,比如 if statement,可以用条件表达式 (?:) 代替 if else
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

函数的参数有两种类型:required 和 optional

首先列出所有必需的参数,再跟上可选的参数。

可选参数有两种指定方式:可选命名参数 named 和可选位置参数 positional

// 定义函数时用{param1, param2, …}的形式指定 named 参数
enableFlags({bool bold, bool hidden}) {
  // ...
}
// 调用函数时可以使用 paramName: value 的方式
enableFlags(bold: true, hidden: false);

用 [ ] 包裹可选参数:

// 定义函数时用 [param1, param2, …] 的形式指定 positional 参数
String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

assert(say('Bob', 'Howdy') == 'Bob says Howdy');
assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');

默认参数值

在函数定义时可以用 = 给参数指定默认值,这个默认值必须是编译时常量,如果没有指定默认值则为 null。

下面是给 named 参数设置默认值的方法:

void enableFlags({bool bold = false, bool hidden = false}) {
  // ...
}
// bold will be true; hidden will be false.
enableFlags(bold: true);

下面是给 positional 参数设置默认值的方法:

String say(String from, String msg,
    [String device = 'carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

assert(say('Bob', 'Howdy') ==
    'Bob says Howdy with a carrier pigeon');

也可以传递 lists 或者 maps 作为默认值:

void doStuff({
    List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}

Main() 函数

每个 app 都必需有一个 main() 函数,main() 返回 void 而且有一个可选的 List字符参数列表。

void main() {
  querySelector("#sample_text_id")
    ..text = "Click me!"
    ..onClick.listen(reverseText);
}
// .. 是级联 (cascade) 操作符,可以对单个对象执行多个操作

给 main() 传入参数:

// 运行 app 时传入参数:dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

将函数作为参数传给其他函数或者赋给变量:

printElement(element) {
  print(element);
}
var list = [1, 2, 3];
list.forEach(printElement);

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!'; // 一个匿名函数
assert(loudify('hello') == '!!! HELLO !!!');

词法作用域 Lexical scope

Dart 时一门词法作用域的语言,即变量的作用域是静态地决定的,由代码的布局决定的。
词法作用域的函数遇到既不是形参也不是函数内部定义的局部变量的变量时,会去函数定义时的环境中查询。

var topLevel = 1; // 如果去掉这一行,getLevel 报错“No top-level getter 'topLevel' declared”
void getLevel() {
  print(topLevel);
}

main () {
  var topLevel = 2;
  getLevel(); // 输出 1,因为 getsome() 定义时 topLevel=1
}

词法闭包

指的是一个函数可以访问其语法作用域内的变量,即使这个函数是在变量本身的作用域之外被调用的。
下面的例子中 makeAdder() 捕获了变量 addBy,不管返回的函数在哪里被调用,它都可以使用 addBy:

Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

main() {
  var add2 = makeAdder(2);
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

函数的等价性测试

下面是关于顶层函数、静态方法和实例方法的等价性测试

foo() {} // 一个顶层函数

class SomeClass {
  static void bar() {} // 一个静态方法
  void baz() {} // 一个实例方法
}

main () {
  var x;

  // 比较顶级函数
  x = foo;
  assert(foo == x); // true

  // 比较静态方法
  x = SomeClass.bar;
  assert(SomeClass.bar == x); // true

  // 比较实例方法
  var v = new SomeClass(); // SomeClass 的实例 1
  var w = new SomeClass(); // SomeClass 的实例 2
  var y = w;
  x = w.baz;

  // 这些闭包引用了相同的示例对象(A 的实例 2),所以它们是等价的
  assert(y.baz == x); // true

  // 这些闭包引用的是不同实例,所以它们不等价
  assert(v.baz != w.baz); // true
}

函数返回值

所有函数都会返回一个值,如果没有指定返回值,函数将会在函数体末尾隐式地添加“return null”

void baz() {}
main () {
  print(baz()); // 输出 null
}

操作符 Operators

Dart 定义了以下操作符,你可以 重写这些操作符

介绍符号
一元后缀符expr++ 、 expr– 、 () 、 [] 、 . 、 ?.
一元前缀符-expr 、 !expr 、 ~expr 、 ++expr 、 —expr
乘法类型* 、 / 、 % 、 ~/
加法类型+ 、 -
位操作符<< 、 >>
按位与&
按位异或^
按为或I
比较和类型测试>= 、 > 、 <= 、 < 、 as 、 is 、 is!
等价== 、 !=
逻辑与&&
逻辑或II
null 分配符??
条件运算符expr1 ? expr2 : expr3
级联运算符..
赋值= 、 *= 、 /= 、 ~/= 、 %= 、 += 、 -= 、 <<= 、 >>= 、 &= 、 ^=

算术运算符

/ 返回 double 型,~/ 返回整型的除数,% 返回除法的余数:

assert(5 / 2 == 2.5); // 结果是 double 类型
assert(5 ~/ 2 == 2); // 结果是一个整数
assert(5 % 2 == 1); // 余数

print('5/2 = ${5~/2} 余 ${5%2}'); // 5/2 = 2 余 1

自增自减

var a, b;

a = 0;
b = ++a; // 在 b 获得其值前先自增 a
assert(a == b); // 1 == 1

a = 0;
b = a++; // 在 b 获得其值后自增 a
assert(a != b); // 1 != 0

a = 0;
b = --a; // 在 b 获得其值前自减 a
assert(a == b); // -1 == -1

a = 0;
b = a--; // 在 b 获得其值后自减 a
assert(a != b); // -1 != 0

类型测试操作符

  • as 类型转换
  • is 当对象是相应类型时返回 true
  • is!当对象不是相应类型时返回 true
    如果 obj 实现了 T 所定义的借口,那么 obj is T 将返回 true。比如,obj is Object 必然返回 true。
    使用 as 操作符可以把一个对象转换为特定类型。一般来说,如果在 is 测试之后还有一些关于对象的表达式,你可以把 as 当做是 is 测试的一种简写:
if (emp is Person) { // 类型检查
  emp.firstName = 'Bob';
}
// as 化简代码
(emp as Person).firstName = 'Bob';

上面两段代码并不相等。如果 emp 的值为 null 或者不是 Person 的一个对象,第一段代码不会做任何事情,第二段代码将会报错 。

分配符

a = value;   // 将 value 赋给 a
b ??= value; // 如果 b 为 null 将 value 赋给 b,否则 b 值不变

条件表达式

  • condition ? expr1 : expr2 条件为真返回表达式 1,否则返回表达式 2
  • expr1 ?? expr2 表达式 1 为 null 则返回表达式 2,否则返回表达式 1
String toString() => msg ?? super.toString();

// 等价于
String toString() => msg == null ? super.toString() : msg;

级联操作符

允许你在单个对象的成员上执行多个操作

querySelector('#button') // Get an object.
  ..text = 'Confirm'   // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

// 等价于
var button = querySelector('#button');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

级联操作符可以嵌套

final addressBook = (
  new AddressBookBuilder()
  ..name = 'jenny'
  ..email = 'jenny@example.com'
  ..phone = (
    new PhoneNumberBuilder()
    ..number = '415-555-0100'
    ..label = 'home'
  ).build()
).build();

在返回对象的函数上使用级联要格外注意,下面的例子中 sb.write() 返回 void,不能在 void 上构建级联

var sb = new StringBuffer();
sb.write('foo')..write('bar'); // 无效

严格意义上讲,级联操作符 .. 不算一个操作符,它属于 Dart 语法的一部分,应该算作一个语法

其他操作符

  • 一个点. 代表成员访问,比如 foo.bar 从 foo 中选择了 bar 属性
  • 问号加上一个点 ?. 代表条件成员访问,?. 之前的操作数可以为空,比如 foo?.bar 从 foo 中选择属性 bar,当 foo 为空时则不访问 bar

控制流语句 Control flow statements

  • if else
  • for 循环
  • while 和 do while 循环
  • break 和 continue
  • switch case
  • assert

for 循环

Dart 的 for 循环闭包中可以获取 index 的值,就像下面的例子输出下标 0,1,2;而在 javaScript 中,下面的例子输出 3,3,3,如果想输出 0,1,2 需要把 var 改成 let

// in Dart
var callbacks = [];
for (var i = 0; i < 3; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c()); // 输出 0,1,2

// in javaScript
var callbacks = [];
for (var i = 0; i < 3; i++) { // 改成 let 输出 0,1,2
  callbacks.push(() => console.log(i));
}
callbacks.forEach((c) => c()); // 输出 3,3,3

可迭代的元素可以用 forEach() 方法遍历,也可以用 for-in

var collection = [0, 1, 2];
for (var x in collection) {
  print(x);
}

break 和 continue

用 break 退出循环,循环结束

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

用 continue 跳出这一次循环,循环不结束

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

通过迭代可以简化上述代码

candidates.where((c) => c.yearsExperience >= 5).forEach((c) => c.interview());

转换语句

可以使用 continue 和标签来跳转

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

一个 case 分句可以含有局部变量,该局部变量仅仅只在此分句范围内可见

assert

如果一个布尔条件值为 false,使用 assert 语句来中断正常执行的代码。在 assert 语句后面的括号中,你可以加入任何表示布尔值或者函数的表达式。如果表达式的值或者函数返回值 true,则 assert 语句成功并继续执行代码。如果值为 false,则 assert 语句失败并抛出一个异常 (an AssertionError)

// 确保这个变量不为空值.
assert(text != null);

// 确保这个变量小于 100.
assert(number < 100);

// 确保它是一个 https 协议类型的 URL.
assert(urlString.startsWith(‘https’));

⚠️注意:assert 语句仅仅只能在调试模式下使用,在生产模式下没有任何作用。

要将消息附加到断言,添加一个字符串作为第二个参数,当第一个参数为 false 时第二个参数会随错误一起抛出

assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');

异常 Exception

throw 语句

抛出了一个异常:

throw new FormatException('Expected at least 1 section');

也可以将任意对象作为异常抛出:

throw 'Out of llamas!';

因为抛出异常的语句是个表达式,所以可以写在箭头函数里:

distanceTo(Point other) => throw new UnimplementedError();

on catch 语句

捕获一个异常:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

为了处理含有多种类型异常的代码,你可以选择多个 catch 子句。第一个匹配抛出对象类型的 catch 子句将会处理这个异常。如果 catch 子句未说明所捕获的异常类型,这个子句就可处理任何被抛出的对象:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 一个具体异常
  buyMoreLlamas();
} on Exception catch (e) {
  // 任意一个异常
  print('Unknown exception: $e');
} catch (e,s) {
  // 非具体类型
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

catch() 可以带两个参数,第二个参数代表堆栈跟踪 stack trace

要处理部分异常,同时允许它传播,可以使用 rethrow 关键字:

final foo = '';

void misbehave() {
  try {
    foo = "You can't change a final variable's value.";
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

上面的例子输出两条 print 语句,如果去掉 rethrow 则只输出第一个 print 语句

finally 语句

如果没有 catch 匹配子句的异常, finally 子句运行以后异常将被传播:

try {
  breedMoreLlamas();
} finally {
  // 即使抛出一个异常时也会进行清理
  cleanLlamaStalls();
}

try {
  breedMoreLlamas();
} catch(e) {
  print('Error: $e');  // 先处理异常
} finally {
  cleanLlamaStalls();  // 然后清理
}
文章目录
  1. 函数 Functions
    1. 函数的参数有两种类型:required 和 optional
    2. 默认参数值
    3. Main() 函数
    4. 词法作用域 Lexical scope
    5. 词法闭包
    6. 函数的等价性测试
    7. 函数返回值
  2. 操作符 Operators
    1. 算术运算符
    2. 自增自减
    3. 类型测试操作符
    4. 分配符
    5. 条件表达式
    6. 级联操作符
    7. 其他操作符
  3. 控制流语句 Control flow statements
    1. for 循环
    2. break 和 continue
    3. 转换语句
    4. assert
  4. 异常 Exception
    1. throw 语句
    2. on catch 语句
    3. finally 语句